﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using System.Reflection;
using System.ComponentModel;
using System.Security.Permissions;

namespace Fonlow.CommandLineGui
{
    /// <summary>
    /// Model of a command line with parameters.
    /// Read a command and initialize a model.
    /// </summary>
    [EnvironmentPermissionAttribute(SecurityAction.LinkDemand | SecurityAction.InheritanceDemand, Unrestricted = true)]
    public abstract class CommandBase
    {
        public abstract string CommandName
        {
            get;
        }

        /// <summary>
        /// The object could be created internally or obtained externally.
        /// </summary>
        public object CommandOptions { get; private set; }

        protected CommandBase()
        {
            CommandOptions = CreateOptions();
            propertiesArray = CommandOptions.GetType().GetProperties();
        }

        /// <summary>
        /// Derived class should have an object class defining properties as the options of the command.
        /// </summary>
        /// <returns></returns>
        protected abstract object CreateOptions();

        /// <summary>
        /// Load from a full command line, and parse the parameters into the model.
        /// </summary>
        /// <param name="commandLine"></param>
        /// <returns></returns>
        public abstract bool Load(string commandLine);

        /// <summary>
        /// Command line built from the model.
        /// </summary>
        public abstract string CommandLine { get; }

        public abstract string Arguments { get; }

        /// <summary>
        /// All properties of options.
        /// </summary>
        PropertyInfo[] propertiesArray;

        /// <summary>
        /// Get display name of a property
        /// </summary>
        /// <param name="propertyInfo"></param>
        /// <returns>If DisplayNameAttribute does not exist, the property name will be used.</returns>
        protected static string GetDisplayName(MemberInfo propertyInfo)
        {
            object[] objects = propertyInfo.GetCustomAttributes(typeof(DisplayNameAttribute), false);
            if (objects.Length == 1)
            {
                return (objects[0] as DisplayNameAttribute).DisplayName;
            }
            return propertyInfo.Name;
        }

        /// <summary>
        /// Get default value of a property.
        /// </summary>
        /// <param name="propertyInfo"></param>
        /// <returns>Null if the default value is not defined. Empty string if the type is string. 0 if the type is int.</returns>
        protected static object GetDefaultValue(PropertyInfo propertyInfo)
        {
            object[] objects = propertyInfo.GetCustomAttributes(typeof(DefaultValueAttribute), false);
            if (objects.Length == 1)
            {
                return (objects[0] as DefaultValueAttribute).Value;
            }

            if (propertyInfo.PropertyType == typeof(string))
            {
                return String.Empty;
            }

            if (propertyInfo.PropertyType == typeof(int))
            {
                return 0;
            }

            return null;
        }

        static string GetHelp(PropertyInfo propertyInfo)
        {
            object[] objects = propertyInfo.GetCustomAttributes(typeof(DescriptionAttribute), false);
            if (objects.Length == 1)
            {
                return (objects[0] as DescriptionAttribute).Description;
            }
            return null;
        }

        protected void ResetOptions()
        {
            foreach (PropertyInfo propertyInfo in propertiesArray)
            {
                propertyInfo.SetValue(CommandOptions, GetDefaultValue(propertyInfo), null);
            }

        }

        /// <summary>
        /// This is for DCOPY:T which format is inconsistent against all others.
        /// </summary>
        /// <param name="s"></param>
        /// <returns></returns>
        static string TrimColumnRight(string s)
        {
            string[] ss = s.Split(new char[] { ':' });
            return ss[0];
        }

        static TypeConverterAttribute GetTypeConverterAttribute(Type t)
        {
            AttributeCollection attributeCollection = TypeDescriptor.GetAttributes(t);
            return attributeCollection[typeof(TypeConverterAttribute)] as TypeConverterAttribute;
        }

        static TypeConverter GetTypeConverter(Type t)
        {
            TypeConverterAttribute converterAttribute = GetTypeConverterAttribute(t);
            if (converterAttribute != null)
            {
                Type converterType = Type.GetType(converterAttribute.ConverterTypeName);
                TypeConverter converter = Activator.CreateInstance(converterType) as TypeConverter;
                return converter;
            }
            else
                return null;
        }

        protected bool AssignParameter(string parameterName, string parameterRest)
        {
            foreach (PropertyInfo propertyInfo in propertiesArray)
            {
                if (parameterName.Equals(TrimColumnRight(GetDisplayName(propertyInfo)), StringComparison.CurrentCultureIgnoreCase))
                {
                    if (propertyInfo.PropertyType == typeof(bool))
                    {
                        propertyInfo.SetValue(CommandOptions, true, null);
                    }
                    else if (propertyInfo.PropertyType == typeof(string))
                    {
                        propertyInfo.SetValue(CommandOptions, parameterRest, null);
                    }
                    else if (propertyInfo.PropertyType == typeof(int))
                    {
                        int intP;
                        if (Int32.TryParse(parameterRest, out intP))
                            propertyInfo.SetValue(CommandOptions, intP, null);
                        else
                            System.Diagnostics.Trace.WriteLine(String.Format("Parameter {0} has invalid value {1}", parameterName, parameterRest));
                    }
                    else
                    {
                        TypeConverter converter = GetTypeConverter(propertyInfo.PropertyType);
                        if (converter != null)// The property is decorated with TypeConverterAttribute
                        {
                            object convertedValue = converter.ConvertFromString(parameterRest);
                            if (convertedValue != null)
                            {
                                propertyInfo.SetValue(CommandOptions, convertedValue, null);
                            }
                        }
                        else
                            propertyInfo.SetValue(CommandOptions, parameterRest, null);
                    }

                    return true;
                }
            }

            return false;
        }

        public string FindParameterHelp(string parameterName)
        {
            foreach (PropertyInfo propertyInfo in propertiesArray)
            {
                if (parameterName.Equals(TrimColumnRight(GetDisplayName(propertyInfo)), StringComparison.CurrentCultureIgnoreCase))
                {
                    return GetHelp(propertyInfo);
                }

            }
            return null;
        }
    }
}
